1 Read the data

library(tidyverse)
library(data.table)
library(dplyr)
library(readxl)
library(caret)
library(magrittr)
ptm<-proc.time()
f<-"/home/bizon/Documents/nuMoM2b_Dataset_NICHD_Data_Challenge.csv"
mu_data_df <- fread(f)
Warning in fread(f) :
  Detected 11717 column names but the data has 11633 columns. Filling rows automatically. Set fill=TRUE explicitly to avoid this warning.
proc.time()-ptm
   user  system elapsed 
  7.186   0.442   4.052 

1.1 Set up easy in-the-app viewing of the accompanying coding and info spreadsheets

I want to be able to look up quickly and select the correct predictor and response variables directly inside this R Studio environment. Eventually, this could be extended to allow variable selection to pass them on to “predictors” and “response” variables used in the various machine learning (ML) models below.

For now, we are simply manually selecting the clinically/medically meaningful predictors/response variables, check them for artifacts, clean up those artifacts as appropriate and proceed to ML modeling to test our hypotheses.


ptm<-proc.time()
f1_info<-"/home/bizon/Documents/mu2b/nuMoM2b_Dataset_Information.xlsx"
f2_code<-"/home/bizon/Documents/mu2b/nuMoM2b_Codebook_NICHD_Data_Challenge.xlsx"
mu_info_df <- read_excel(f1_info)
New names:
* `` -> ...2
* `` -> ...3
* `` -> ...4
* `` -> ...5
* `` -> ...6
* ...
mu_code_df <- read_excel(f2_code)
proc.time()-ptm
   user  system elapsed 
  0.100   0.020   0.119 

Now that we loaded our Information and Code spreadsheets, we can use DT library to view and search in them easily.

Note: it is best to open the output tables in a new window.

library(DT)
datatable(mu_code_df, filter = 'top', options = list(pageLength = 10, autoWidth = TRUE))
Warning in instance$preRenderHook(instance) :
  It seems your data is too big for client-side DataTables. You may consider server-side processing: https://rstudio.github.io/DT/server.html
Warning in instance$preRenderHook(instance) :
  It seems your data is too big for client-side DataTables. You may consider server-side processing: https://rstudio.github.io/DT/server.html
datatable(mu_info_df, filter = 'top', options = list(pageLength = 10, autoWidth = TRUE))

2 Preprocessing

I need to remove NA values from CMAJ01.

Note: this step can be repeated as needed in the preprocessing pipeline for the desired response variable

# select "CMAJ01" | if NA, remove that row 

# remove all rows where the column CMAJ01 has NA
clean_CMAJ01_df<-mu_data_df[!is.na(mu_data_df$CMAJ01), ] 

# The above won't work on an h2o object, but works on a regular dataframe, so reading as df first and then, once preprocessing is done, moving on to h2o
#View(clean_CMAJ01_df$CMAJ01) #super fast Excel-like view of the data

glimpse(clean_CMAJ01_df$CMAJ01)
 int [1:8774] 2 2 2 2 2 2 2 2 2 2 ...
count(clean_CMAJ01_df, CMAJ01)
nrow(clean_CMAJ01_df[mu_data_df$CMAJ01])
[1] 9289
sum(is.na(clean_CMAJ01_df$CMAJ01))
[1] 0

Confirming that we removed all NAs for CMAJ01:

glimpse: int [1:8774] 2 2 2 2 2 2 2 2 2 2 ...
count: [1] 9289
sum is.na: [1] 0
Raw data cleaned with regard to NA values in CMAJ01, my response variable. Using this for modeling.

+===============================================================================+ +——————————————————————————-+

CMAJ01

<int>

n

<int>
1 154
2 8620

3 Load h2o for Machine Learning

library(h2o)
h2o.init()

H2O is not running yet, starting it now...

Note:  In case of errors look at the following log files:
    /tmp/Rtmp7CbtxK/filed496a07bb37/h2o_bizon_started_from_r.out
    /tmp/Rtmp7CbtxK/filed492544c91d/h2o_bizon_started_from_r.err
openjdk version "11.0.11" 2021-04-20
OpenJDK Runtime Environment (build 11.0.11+9-Ubuntu-0ubuntu2.18.04)
OpenJDK 64-Bit Server VM (build 11.0.11+9-Ubuntu-0ubuntu2.18.04, mixed mode, sharing)

Starting H2O JVM and connecting: .. Connection successful!

R is connected to the H2O cluster: 
    H2O cluster uptime:         1 seconds 98 milliseconds 
    H2O cluster timezone:       America/Vancouver 
    H2O data parsing timezone:  UTC 
    H2O cluster version:        3.34.0.3 
    H2O cluster version age:    5 days  
    H2O cluster name:           H2O_started_from_R_bizon_qvo599 
    H2O cluster total nodes:    1 
    H2O cluster total memory:   15.68 GB 
    H2O cluster total cores:    16 
    H2O cluster allowed cores:  16 
    H2O cluster healthy:        TRUE 
    H2O Connection ip:          localhost 
    H2O Connection port:        54321 
    H2O Connection proxy:       NA 
    H2O Internal Security:      FALSE 
    H2O API Extensions:         Amazon S3, XGBoost, Algos, AutoML, Core V3, TargetEncoder, Core V4 
    R Version:                  R version 4.1.1 (2021-08-10) 

Reading in the preprocessed dataset.

ptm<-proc.time()
mu_data <- as.h2o(clean_CMAJ01_df)

  |                                                                                                               
  |                                                                                                         |   0%
  |                                                                                                               
  |=========================================================================================================| 100%
proc.time()-ptm
   user  system elapsed 
  8.194   0.597  16.368 

3.1 Set predictors and response; set response as a factor

  • maternal outcome: mortality (CMAE14): don’t have enough data to build a binary outcome prediction model | skip;

  • First, exploring the impact of demographics on readmission rate (CMAJ01) as a measure of morbidity:


mu_data[,"CMAJ01"]<-as.factor(mu_data[,"CMAJ01"]) 

# predictors "participant", "demographics" dataset (column A) includes the relevant features
# concludes the variables from "demographics" dataset; model as is and test later by adding participant features from the "CMA" set

predictors<-c("CRace",
              "Race",
              "eRace",
              "eHispanic",
              "BMI",
              "BMI_Cat",
              "Education",
              "GravCat",
              "SmokeCat1",
              "SmokeCat2",
              "SmokeCat3",
              "Ins_Govt",
              "Ins_Mil",
              "Ins_Comm",
              "Ins_Pers",
              "Ins_Othr",
              "PctFedPoverty",
              "poverty"                        
              ) #makes a list of predicting variables

response<-"CMAJ01"
mu_data

[8776 rows x 11717 columns] 
h2o.nrow(mu_data["CMAJ01"])
[1] 8776
h2o.group_by(data=mu_data, by="CMAJ01", nrow("CMAJ01"))

[3 rows x 2 columns] 

In the raw dataset, we had 8786 cases of “CMAE14” are coded as 2 = not died, there are no cases to study this outcome.

In the raw dataset, we had for “CMAJ01”, we have a slightly better situation: 1, yes = 154 cases of readmission, 2, No, 8620 cases.

Needed to handle NaN and 4th row. This is best done on the R data frame which is what we did above in Preprocessing step.

head(mu_data, cache=TRUE)
# agg <- h2o.aggregator(training_frame = mu_data,
#                       target_num_exemplars = 9000,
#                       rel_tol_num_exemplars = 0.5,
#                       categorical_encoding = "Eigen")
# new_df<-h2o.aggregated_frame(agg)
# new_df

Aggregator destroys this dataframe. Skip this step.

4 Train the DRF model

(first, let’s do a “quick&dirty” way = no hold-out dataset to get some sense of the data from ML standpoint)


test_mother_mu_data_RFmodel<-h2o.randomForest(x=predictors,y=response,training_frame = mu_data, nfolds=10,seed = 1234)

  |                                                                                                               
  |                                                                                                         |   0%
  |                                                                                                               
  |=================                                                                                        |  16%
  |                                                                                                               
  |==============================                                                                           |  28%
  |                                                                                                               
  |==========================================                                                               |  40%
  |                                                                                                               
  |========================================================                                                 |  53%
  |                                                                                                               
  |==============================================================================================           |  89%
  |                                                                                                               
  |======================================================================================================== |  99%
  |                                                                                                               
  |=========================================================================================================| 100%

4.1 Show the model’s performance

AUCpr:  0.9851791 for prediction of hospital readmission from demographics

test_mother_mu_data_RFmodel
Model Details:
==============

H2OBinomialModel: drf
Model ID:  DRF_model_R_1634088199961_1359 
Model Summary: 


H2OBinomialMetrics: drf
** Reported on training data. **
** Metrics reported on Out-Of-Bag training samples **

MSE:  0.01951532
RMSE:  0.1396972
LogLoss:  0.2031171
Mean Per-Class Error:  0.5
AUC:  0.5781258
AUCPR:  0.9858659
Gini:  0.1562517
R^2:  -0.1317303

Confusion Matrix (vertical: actual; across: predicted) for F1-optimal threshold:

Maximum Metrics: Maximum metrics at their respective thresholds

Gains/Lift Table: Extract with `h2o.gainsLift(<model>, <data>)` or `h2o.gainsLift(<model>, valid=<T/F>, xval=<T/F>)`

H2OBinomialMetrics: drf
** Reported on cross-validation data. **
** 10-fold cross-validation on training data (Metrics computed for combined holdout predictions) **

MSE:  0.0190441
RMSE:  0.1380004
LogLoss:  0.1444208
Mean Per-Class Error:  0.5
AUC:  0.570256
AUCPR:  0.9852251
Gini:  0.1405121
R^2:  -0.1044036

Confusion Matrix (vertical: actual; across: predicted) for F1-optimal threshold:

Maximum Metrics: Maximum metrics at their respective thresholds

Gains/Lift Table: Extract with `h2o.gainsLift(<model>, <data>)` or `h2o.gainsLift(<model>, valid=<T/F>, xval=<T/F>)`
Cross-Validation Metrics Summary: 

That was a quick test.

Now I need to create a hold-out dataset first; repeat the above then as test:validation.

See here, here and here

4.2 Split the data 0.8 for training:validation

mother_mu_data_split <- h2o.splitFrame(mu_data, ratios=0.8, seed = 1)
train=mother_mu_data_split[[1]]
valid=mother_mu_data_split[[2]]

4.3 Train the DRF model on the training dataset = 0.8

ptm<-proc.time()
mother_mu_data_RFmodel<-h2o.randomForest(x=predictors,y=response,training_frame = train, nfolds=10,seed = 1234)

  |                                                                                                               
  |                                                                                                         |   0%
  |                                                                                                               
  |====================                                                                                     |  19%
  |                                                                                                               
  |================================                                                                         |  31%
  |                                                                                                               
  |============================================                                                             |  42%
  |                                                                                                               
  |========================================================                                                 |  53%
  |                                                                                                               
  |=============================================================================================            |  88%
  |                                                                                                               
  |====================================================================================================     |  96%
  |                                                                                                               
  |=========================================================================================================| 100%
proc.time()-ptm
   user  system elapsed 
  0.883   0.032   8.377 

4.4 Predict using the DRF model on the testing dataset =0.2

Yields very good AUCpr=0.984

ptm<-proc.time()

mother_mu_data_predict<-h2o.predict(object=mother_mu_data_RFmodel, newdata=valid)

  |                                                                                                               
  |                                                                                                         |   0%
  |                                                                                                               
  |=========================================================================================================| 100%
mother_mu_data_RFmodel
Model Details:
==============

H2OBinomialModel: drf
Model ID:  DRF_model_R_1634088199961_2019 
Model Summary: 


H2OBinomialMetrics: drf
** Reported on training data. **
** Metrics reported on Out-Of-Bag training samples **

MSE:  0.01868367
RMSE:  0.1366882
LogLoss:  0.283439
Mean Per-Class Error:  0.5
AUC:  0.5435738
AUCPR:  0.9840289
Gini:  0.0871475
R^2:  -0.1232257

Confusion Matrix (vertical: actual; across: predicted) for F1-optimal threshold:

Maximum Metrics: Maximum metrics at their respective thresholds

Gains/Lift Table: Extract with `h2o.gainsLift(<model>, <data>)` or `h2o.gainsLift(<model>, valid=<T/F>, xval=<T/F>)`

H2OBinomialMetrics: drf
** Reported on cross-validation data. **
** 10-fold cross-validation on training data (Metrics computed for combined holdout predictions) **

MSE:  0.01836745
RMSE:  0.1355266
LogLoss:  0.1634527
Mean Per-Class Error:  0.5
AUC:  0.5551044
AUCPR:  0.9843388
Gini:  0.1102087
R^2:  -0.1042151

Confusion Matrix (vertical: actual; across: predicted) for F1-optimal threshold:

Maximum Metrics: Maximum metrics at their respective thresholds

Gains/Lift Table: Extract with `h2o.gainsLift(<model>, <data>)` or `h2o.gainsLift(<model>, valid=<T/F>, xval=<T/F>)`
Cross-Validation Metrics Summary: 
proc.time()-ptm
   user  system elapsed 
  0.056   0.004   0.089 

4.4.1 AUROCpr for predicting re-hospitalization based on patient demographics alone


mod=mother_mu_data_RFmodel

perf <- h2o.performance(mod,valid)

metrics <- as.data.frame(h2o.metric(perf))

metrics %>%
  ggplot(aes(recall,precision)) + 
  geom_line() +
  theme_minimal()

4.4.2 Explain the model


ptm<-proc.time()

# toggle progress bar if desired:
# h2o.show_progress() 

exp <-h2o.explain(object=mother_mu_data_RFmodel, newdata=valid)
print(exp)


Confusion Matrix
================

> Confusion matrix shows a predicted class vs an actual class.



DRF_model_R_1634088199961_2019
------------------------------
Confusion Matrix (vertical: actual; across: predicted)  for max f1 @ threshold = 0.559264203719795:


Variable Importance
===================

> The variable importance plot shows the relative importance of the most important variables in the model.



SHAP Summary
============

> SHAP summary plot shows the contribution of the features for each instance (row of data). The sum of the feature contributions and the bias term is equal to the raw prediction of the model, i.e., prediction before applying inverse link function.



Partial Dependence Plots
========================

> Partial dependence plot (PDP) gives a graphical depiction of the marginal effect of a variable on the response. The effect of a variable is measured in change in the mean response. PDP assumes independence between the feature for which is the PDP computed and the rest.

proc.time()-ptm
   user  system elapsed 
 57.874   3.947  82.542 

4.4.3 Statistics summary


results_df <- function(h2o_model) {
  h2o_model@model$cross_validation_metrics_summary %>% 
    as.data.frame() %>% 
    select(-mean, -sd) %>% 
    t() %>% 
    as.data.frame() %>% 
    mutate_all(as.character) %>% 
    mutate_all(as.numeric) -> k
  
  k %>% 
    select(Accuracy = accuracy,
           prAUC = pr_auc,
           Precision = precision,
           Specificity = specificity,
           Recall = recall,
           Logloss = logloss) %>% 
  return()
}

# Using function
results_df(mod) -> outcome

# Outcome 
outcome %>% 
  gather(Metrics, Values) %>% 
  ggplot(aes(Metrics, Values, fill = Metrics, color = Metrics)) +
  geom_boxplot(alpha = 0.3, show.legend = FALSE) + 
  facet_wrap(~ Metrics, scales = "free") + 
  labs(title = "Performance of our ML model using H2o package ",
       caption = "Data Source: NICHD Decoding Maternal Morbidity Data Challenge\nCreated by Martin Frasch (further credit to https://bit.ly/3BpPqcb)") +
  theme_minimal()


# Statistics summary
outcome %>% 
  gather(Metrics, Values) %>% 
  group_by(Metrics) %>% 
  summarise_each(funs(mean, median, min, max, sd, n())) %>% 
  mutate_if(is.numeric, function(x) {round(100*x, 2)}) %>%
  knitr::kable(col.names = c("Criterion", "Mean", "Median", "Min", "Max", "SD", "N"))
Warning: `summarise_each_()` was deprecated in dplyr 0.7.0.
Please use `across()` instead.
This warning is displayed once every 8 hours.
Call `lifecycle::last_lifecycle_warnings()` to see where this warning was generated.
Warning: `funs()` was deprecated in dplyr 0.8.0.
Please use a list of either functions or lambdas: 

  # Simple named list: 
  list(mean = mean, median = median)

  # Auto named with `tibble::lst()`: 
  tibble::lst(mean, median)

  # Using lambdas
  list(~ mean(., trim = .2), ~ median(., na.rm = TRUE))
This warning is displayed once every 8 hours.
Call `lifecycle::last_lifecycle_warnings()` to see where this warning was generated.
Criterion Mean Median Min Max SD N
Accuracy 98.30 98.24 97.83 99.03 0.34 1000
Logloss 16.63 14.30 9.15 30.47 7.38 1000
prAUC 98.46 98.51 97.55 99.14 0.52 1000
Precision 98.30 98.24 97.83 99.03 0.34 1000
Recall 100.00 100.00 100.00 100.00 0.00 1000
Specificity 0.00 0.00 0.00 0.00 0.00 1000

4.5 Building an interpretable decision tree model

Using the balance_classes term available in this model allows to better control for the unbalanced data we are dealing with here, because the re-hospitalization rate is ~2%

Source


maternal_dt_model<-h2o.gbm(x=predictors,y=response,training_frame = train, validation_frame = valid, balance_classes = TRUE, seed = 1234, nfolds=10)

# GBM hyperparamters
gbm_params = list(max_depth = seq(2, 10))

# Train and validate a cartesian grid of GBMs
gbm_grid = h2o.grid("gbm", x = predictors, y = response,
                    grid_id = "gbm_grid_1tree8",
                    training_frame = train,
                    validation_frame = valid,
                    balance_classes = TRUE,
                    ntrees = 1, min_rows = 1, sample_rate = 1, col_sample_rate = 1,
                    learn_rate = .01, seed = 1234, 
                    hyper_params = gbm_params)

gbm_gridperf = h2o.getGrid(grid_id = "gbm_grid_1tree8",
                           sort_by = "auc",
                           decreasing = TRUE)

# what is the performance of this GBM?
maternal_dt_model

We obtain prAUC=0.98

gbm_gridperf

Inflection point is at max_depth=8


maternal_1_tree = h2o.gbm(x = predictors, y = response, 
                        training_frame = train, balance_classes = TRUE,
                        ntrees = 1, min_rows = 1, sample_rate = 1, col_sample_rate = 1,
                        max_depth = 8,
                  # use early stopping once the validation AUC doesn't improve by at least 0.01%
                  # for 5 consecutive scoring events
                        stopping_rounds = 3, stopping_tolerance = 0.01, 
                        stopping_metric = "AUC", 
                        seed = 1)
maternal_1_tree
AUCPR:  0.882762
maternal_Tree = h2o.getModelTree(model = maternal_1_tree, tree_number = 1)

# Visualizing H2O Trees

library(data.tree)

createDataTree <- function(h2oTree) {
  
  h2oTreeRoot = h2oTree@root_node
  
  dataTree = Node$new(h2oTreeRoot@split_feature)
  dataTree$type = 'split'
  
  addChildren(dataTree, h2oTreeRoot)
  
  return(dataTree)
}

addChildren <- function(dtree, node) {
  
  if(class(node)[1] != 'H2OSplitNode') return(TRUE)
  
  feature = node@split_feature
  id = node@id
  na_direction = node@na_direction
  
  if(is.na(node@threshold)) {
    leftEdgeLabel = printValues(node@left_levels, na_direction=='LEFT', 4)
    rightEdgeLabel = printValues(node@right_levels, na_direction=='RIGHT', 4)
  }else {
    leftEdgeLabel = paste("<", node@threshold, ifelse(na_direction=='LEFT',',NA',''))
    rightEdgeLabel = paste(">=", node@threshold, ifelse(na_direction=='RIGHT',',NA',''))
  }
  
  left_node = node@left_child
  right_node = node@right_child
  
  if(class(left_node)[[1]] == 'H2OLeafNode')
    leftLabel = paste("prediction:", left_node@prediction)
  else
    leftLabel = left_node@split_feature
  
  if(class(right_node)[[1]] == 'H2OLeafNode')
    rightLabel = paste("prediction:", right_node@prediction)
  else
    rightLabel = right_node@split_feature
  
  if(leftLabel == rightLabel) {
    leftLabel = paste(leftLabel, "(L)")
    rightLabel = paste(rightLabel, "(R)")
  }
  
  dtreeLeft = dtree$AddChild(leftLabel)
  dtreeLeft$edgeLabel = leftEdgeLabel
  dtreeLeft$type = ifelse(class(left_node)[1] == 'H2OSplitNode', 'split', 'leaf')
  
  dtreeRight = dtree$AddChild(rightLabel)
  dtreeRight$edgeLabel = rightEdgeLabel
  dtreeRight$type = ifelse(class(right_node)[1] == 'H2OSplitNode', 'split', 'leaf')
  
  addChildren(dtreeLeft, left_node)
  addChildren(dtreeRight, right_node)
  
  return(FALSE)
}

printValues <- function(values, is_na_direction, n=4) {
  l = length(values)
  
  if(l == 0)
    value_string = ifelse(is_na_direction, "NA", "")
  else
    value_string = paste0(paste0(values[1:min(n,l)], collapse = ', '),
                          ifelse(l > n, ",...", ""),
                          ifelse(is_na_direction, ", NA", ""))
  
  return(value_string)
}

This decision tree, also supplied as PDF, is meant to help build intuition about how the model.


library(DiagrammeR)

# customized DT for our H2O model

maternal_mu2DataTree = createDataTree(maternal_Tree)

GetEdgeLabel <- function(node) {return (node$edgeLabel)}
GetNodeShape <- function(node) {switch(node$type, 
                                       split = "diamond", leaf = "oval")}
GetFontName <- function(node) {switch(node$type, 
                                      split = 'Palatino-bold', 
                                      leaf = 'Palatino')}
SetEdgeStyle(maternal_mu2DataTree, fontname = 'Palatino-italic', 
             label = GetEdgeLabel, labelfloat = TRUE,
             fontsize = "26", fontcolor='royalblue4')
SetNodeStyle(maternal_mu2DataTree, fontname = GetFontName, shape = GetNodeShape, 
             fontsize = "26", fontcolor='royalblue4',
             height="0.75", width="1")

SetGraphStyle(maternal_mu2DataTree, rankdir = "LR", dpi=70.)

plot(maternal_mu2DataTree, output = "graph")
ptm<-proc.time()

exp_dt<-h2o.explain(maternal_dt_model,valid)

proc.time()-ptm

exp_dt

4.6 Using Naïve Bayes Classifier


# Build and train the model:
mo2b_nb <- h2o.naiveBayes(x = predictors,
                          y = response,
                          training_frame = train,
                          laplace = 0,
                          nfolds = 10,
                          seed = 1234)

  |                                                                                                                       
  |                                                                                                                 |   0%
  |                                                                                                                       
  |=======================================================================================================          |  91%
  |                                                                                                                       
  |=================================================================================================================| 100%
# Eval performance:
perf <- h2o.performance(mo2b_nb)

# Generate the predictions on a test set (if necessary):
pred <- h2o.predict(mo2b_nb, newdata = valid)

  |                                                                                                                       
  |                                                                                                                 |   0%
  |                                                                                                                       
  |=================================================================================================================| 100%
perf
H2OBinomialMetrics: naivebayes
** Reported on training data. **

MSE:  0.2641484
RMSE:  0.5139537
LogLoss:  1.244752
Mean Per-Class Error:  0.5
AUC:  0.6178245
AUCPR:  0.9877748
Gini:  0.235649

Confusion Matrix (vertical: actual; across: predicted) for F1-optimal threshold:

Maximum Metrics: Maximum metrics at their respective thresholds

Gains/Lift Table: Extract with `h2o.gainsLift(<model>, <data>)` or `h2o.gainsLift(<model>, valid=<T/F>, xval=<T/F>)`
AUC:  0.6178245
AUCPR:  0.9877748

# best viewed in a new window or see, please, the PDF included with the submission
exp_nb <- h2o.explain(mo2b_nb,valid)
Warning: StackedEnsemble does not have a variable importance. Picking all columns. Set `columns` to a vector of columns to explain just a subset of columns.

Note the highly variable partial importance of the different socio-demographic characteristics


exp_nb


Confusion Matrix
================

> Confusion matrix shows a predicted class vs an actual class.



NaiveBayes_model_R_1634088199961_6290
-------------------------------------
Confusion Matrix (vertical: actual; across: predicted)  for max f1 @ threshold = 4.35072885475268e-05:


Partial Dependence Plots
========================

> Partial dependence plot (PDP) gives a graphical depiction of the marginal effect of a variable on the response. The effect of a variable is measured in change in the mean response. PDP assumes independence between the feature for which is the PDP computed and the rest.
*** recursive gc invocation
*** recursive gc invocation
*** recursive gc invocation
*** recursive gc invocation
*** recursive gc invocation
*** recursive gc invocation
*** recursive gc invocation
*** recursive gc invocation

4.7 Extending the ML toolbox: Using h2o AutoML mode to find an objectively best performing model

Compare here. The findings are to be interpreted with caution at this stage. Once we obtain a larger external dataset for validation, with a more balanced case distribution, this will become more useful and allow building an inference engine that could be deployed for use. I am presenting this code therefore as a reference for future work.

Nevertheless, it is evident that an optimization even at this stage results in a marked (~60%) improvement of classification prediction performance, up to AUC 76%. This result can vary depending on the run.

Note please, this code runs for ~2 hours on a well-equipped deep learning workstation.

4.7.1 Train in AutoML mode


ptm<-proc.time()

maternal_aml <- h2o.automl(x=predictors,y=response,training_frame = train, max_models = 20, seed = 1)

  |                                                                                                                       
  |                                                                                                                 |   0%
  |                                                                                                                       
  |==                                                                                                               |   2%
18:41:03.771: XGBoost_1_AutoML_2_20211012_184101 [XGBoost def_2] failed: water.exceptions.H2OModelBuilderIllegalArgumentException: Illegal argument(s) for XGBoost model: XGBoost_1_AutoML_2_20211012_184101_cv_1.  Details: ERRR on field: _response_column: Response contains missing values (NAs) - not supported by XGBoost.

  |                                                                                                                       
  |===                                                                                                              |   2%
  |                                                                                                                       
  |===                                                                                                              |   3%
  |                                                                                                                       
  |====                                                                                                             |   3%
  |                                                                                                                       
  |=====                                                                                                            |   5%
  |                                                                                                                       
  |======                                                                                                           |   5%
  |                                                                                                                       
  |======                                                                                                           |   6%
18:46:52.731: XGBoost_2_AutoML_2_20211012_184101 [XGBoost def_1] failed: water.exceptions.H2OModelBuilderIllegalArgumentException: Illegal argument(s) for XGBoost model: XGBoost_2_AutoML_2_20211012_184101_cv_1.  Details: ERRR on field: _response_column: Response contains missing values (NAs) - not supported by XGBoost.

  |                                                                                                                       
  |=========                                                                                                        |   8%
  |                                                                                                                       
  |==========                                                                                                       |   8%
  |                                                                                                                       
  |==========                                                                                                       |   9%
  |                                                                                                                       
  |============                                                                                                     |  10%
  |                                                                                                                       
  |============                                                                                                     |  11%
  |                                                                                                                       
  |==============                                                                                                   |  12%
  |                                                                                                                       
  |==============                                                                                                   |  13%
  |                                                                                                                       
  |===============                                                                                                  |  13%
  |                                                                                                                       
  |=================                                                                                                |  15%
  |                                                                                                                       
  |==================                                                                                               |  16%
  |                                                                                                                       
  |====================                                                                                             |  18%
18:48:11.473: XGBoost_3_AutoML_2_20211012_184101 [XGBoost def_3] failed: water.exceptions.H2OModelBuilderIllegalArgumentException: Illegal argument(s) for XGBoost model: XGBoost_3_AutoML_2_20211012_184101_cv_1.  Details: ERRR on field: _response_column: Response contains missing values (NAs) - not supported by XGBoost.

  |                                                                                                                       
  |=====================                                                                                            |  19%
  |                                                                                                                       
  |======================                                                                                           |  19%
  |                                                                                                                       
  |======================                                                                                           |  20%
  |                                                                                                                       
  |=======================                                                                                          |  20%
  |                                                                                                                       
  |========================                                                                                         |  21%
  |                                                                                                                       
  |=========================                                                                                        |  22%
  |                                                                                                                       
  |==========================                                                                                       |  23%
  |                                                                                                                       
  |============================                                                                                     |  25%
  |                                                                                                                       
  |=============================================                                                                    |  40%
  |                                                                                                                       
  |========================================================                                                         |  50%
  |                                                                                                                       
  |==============================================================                                                   |  54%
  |                                                                                                                       
  |================================================================                                                 |  57%
  |                                                                                                                       
  |======================================================================                                           |  62%
  |                                                                                                                       
  |===========================================================================                                      |  67%
  |                                                                                                                       
  |================================================================================                                 |  71%
19:11:11.167: StackedEnsemble_BestOfFamily_6_AutoML_2_20211012_184101 [StackedEnsemble best_of_family_xgboost (built with xgboost metalearner, using top model from each algorithm type)] failed: java.lang.RuntimeException: water.exceptions.H2OModelBuilderIllegalArgumentException: Illegal argument(s) for XGBoost model: metalearner_xgboost_StackedEnsemble_BestOfFamily_6_AutoML_2_20211012_184101_cv_1.  Details: ERRR on field: _response_column: Response contains missing values (NAs) - not supported by XGBoost.

  |                                                                                                                       
  |====================================================================================                             |  74%
19:11:16.761: StackedEnsemble_AllModels_5_AutoML_2_20211012_184101 [StackedEnsemble all_xgboost (built with xgboost metalearner, using all AutoML models)] failed: java.lang.RuntimeException: water.exceptions.H2OModelBuilderIllegalArgumentException: Illegal argument(s) for XGBoost model: metalearner_xgboost_StackedEnsemble_AllModels_5_AutoML_2_20211012_184101_cv_1.  Details: ERRR on field: _response_column: Response contains missing values (NAs) - not supported by XGBoost.

  |                                                                                                                       
  |=======================================================================================                          |  77%
  |                                                                                                                       
  |=========================================================================================                        |  79%
  |                                                                                                                       
  |=================================================================================================================| 100%
maternal_lb <- maternal_aml@leaderboard

#print(maternal_lb, n = nrow(maternal_lb)) #Print all rows instead of default 6 rows

proc.time()-ptm
    user   system  elapsed 
 465.386   13.888 1852.840 

4.7.2 Validate the combined leader aml model

ptm<-proc.time()

maternal_perf_valid <- h2o.performance(maternal_aml@leader,newdata=valid,xval=FALSE,valid=TRUE)

pred <- h2o.predict(maternal_aml@leader, valid)
h2o.auc(maternal_aml@leader)
# Using function
results_df(maternal_aml@leader) -> outcome

# Outcome 
outcome %>% 
  gather(Metrics, Values) %>% 
  ggplot(aes(Metrics, Values, fill = Metrics, color = Metrics)) +
  geom_boxplot(alpha = 0.3, show.legend = FALSE) + 
  facet_wrap(~ Metrics, scales = "free") + 
  labs(title = "Performance of the best AutoML model using H2o package ",
       caption = "Data Source: NICHD Decoding Maternal Morbidity Data Challenge\nCreated by Martin Frasch (further credit to https://bit.ly/3BpPqcb)") +
  theme_minimal()

Explain the model

We observe no specificity because the dataset is unbalanced such that by luck of draw (when the dataset is split 80:20) we get no true positives.

ptm<-proc.time()
exp <-h2o.explain(maternal_aml@leader, valid)
proc.time()-ptm
print(exp)

5 GPU accelerated ML solutions

Test run from the demo site. Details on setup here.

5.0.1 Testing h2o GPU installation

Requires Python h2o4gpu module. Details of installation here.

  • The R library uses reticulate to point to the correct Python version (3.6)

  • Vignettes | Python demos

library(h2o4gpu)
library(reticulate)  # only needed if using a virtual Python environment or multiple Python versions
use_python("/home/bizon/anaconda3/bin/python3.6", required = TRUE)
#use_virtualenv("/home/bizon/h2o_ml")  # set this to the path of your venv if any is used

# Setup dataset
x <- iris[1:4]
y <- as.integer(iris$Species) - 1

# Initialize and train the classifier
model <- h2o4gpu.random_forest_classifier() %>% fit(x, y)

#  Other ML approaches are also available such as GBM, regression models, unsupervised learning

# Make predictions
predictions <- model %>% predict(x)

5.0.2 Deploying GPU-accelerated ML on our dataset

I am leaving this for future implementations. This is simply to point out the possibility to leverage GPU-equipped deep learning workstations to speed up the model building time on a dataset of this size.

=> See “postpartum depression” notebook for additional models.

sessionInfo()
LS0tCnRpdGxlOiAiTklDSEQgRGVjb2RpbmcgTWF0ZXJuYWwgTW9yYmlkaXR5IERhdGEgQ2hhbGxlbmdlOiBwcmVkaWN0aW9uIG9mIHJlaG9zcGl0YWxpemF0aW9uIgphdXRob3I6IAogIG5hbWU6ICJEci4gTWFydGluIEcuIEZyYXNjaCIKICBhZmZpbGlhdGlvbjogIkhlYWx0aCBTdHJlYW0gQW5hbHl0aWNzIgpkYXRlOiAiYHIgU3lzLkRhdGUoKWAiCm91dHB1dDoKICBodG1sX2RvY3VtZW50OgogICAgdG9jOiB5ZXMKICAgIGRmX3ByaW50OiBwYWdlZAogIGh0bWxfbm90ZWJvb2s6CiAgICB0b2M6IHllcwogICAgdG9jX2Zsb2F0OiB5ZXMKICAgIHRoZW1lOiBwYXBlcgogICAgbnVtYmVyX3NlY3Rpb25zOiB5ZXMKLS0tCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFKQoKIyBiZSBjYXJlZnVsIHdpdGggY2FjaGUgc2V0dGluZzogc2V0IHRvIEZBTFNFIGlmIHlvdSB3YW50IHRvIHJlcnVuIGFsbCBjb2RlIGJlZm9yZSBrbml0dGluZwoKIyBzeW50YXggdG8gdGltZSBleGVjdXRpb24gb2YgYSBjZWxsIGluIFIgfCBhZGRlZCBhcyB0aW1lX2l0IHNuaXBwZXQgfCBpbnZva2Ugd2l0aCBzaGlmdCtUQUIKcHRtPC1wcm9jLnRpbWUoKQojIGluc2VydCBteSBjb2RlCnByb2MudGltZSgpLXB0bQpgYGAKCmBgYHtyLCBpbmNsdWRlPUZBTFNFfQpsb2NhbCh7ciA8LSBnZXRPcHRpb24oInJlcG9zIik7IHJbIkNSQU4iXSA8LSAiaHR0cDovL2NyYW4udXMuci1wcm9qZWN0Lm9yZyI7IG9wdGlvbnMocmVwb3MgPSByKX0pCmlmICghIlIudXRpbHMiICVpbiUgcm93bmFtZXMoaW5zdGFsbGVkLnBhY2thZ2VzKCkpKSBpbnN0YWxsLnBhY2thZ2VzKCJSLnV0aWxzIikKc2V0d2QoIi9ob21lL2Jpem9uL0RvY3VtZW50cy9tdTJiLyIpCm9wdGlvbnMoZWNobz1UUlVFKQpURVNUX1JPT1RfRElSIDwtICIuLiIKYGBgCgojIFJlYWQgdGhlIGRhdGEKCmBgYHtyIGxvYWQgZ2VuZXJhbCBwYWNrYWdlcyBmb3IgaGFuZGxpbmcgYmlnIGRhdGF9CmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KGRhdGEudGFibGUpCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkocmVhZHhsKQpsaWJyYXJ5KGNhcmV0KQpsaWJyYXJ5KG1hZ3JpdHRyKQpgYGAKCmBgYHtyIHJlYWQgdGhlIHJhdyBkYXRhLCBjYWNoZT1UUlVFfQpwdG08LXByb2MudGltZSgpCmY8LSIvaG9tZS9iaXpvbi9Eb2N1bWVudHMvbnVNb00yYl9EYXRhc2V0X05JQ0hEX0RhdGFfQ2hhbGxlbmdlLmNzdiIKbXVfZGF0YV9kZiA8LSBmcmVhZChmKQpwcm9jLnRpbWUoKS1wdG0KYGBgCgojIyBTZXQgdXAgZWFzeSBpbi10aGUtYXBwIHZpZXdpbmcgb2YgdGhlIGFjY29tcGFueWluZyBjb2RpbmcgYW5kIGluZm8gc3ByZWFkc2hlZXRzCgpJIHdhbnQgdG8gYmUgYWJsZSB0byBsb29rIHVwIHF1aWNrbHkgYW5kIHNlbGVjdCB0aGUgY29ycmVjdCBwcmVkaWN0b3IgYW5kIHJlc3BvbnNlIHZhcmlhYmxlcyBkaXJlY3RseSBpbnNpZGUgdGhpcyBSIFN0dWRpbyBlbnZpcm9ubWVudC4gRXZlbnR1YWxseSwgdGhpcyBjb3VsZCBiZSBleHRlbmRlZCB0byBhbGxvdyB2YXJpYWJsZSBzZWxlY3Rpb24gdG8gcGFzcyB0aGVtIG9uIHRvICJwcmVkaWN0b3JzIiBhbmQgInJlc3BvbnNlIiB2YXJpYWJsZXMgdXNlZCBpbiB0aGUgdmFyaW91cyBtYWNoaW5lIGxlYXJuaW5nIChNTCkgbW9kZWxzIGJlbG93LgoKRm9yIG5vdywgd2UgYXJlIHNpbXBseSBtYW51YWxseSBzZWxlY3RpbmcgdGhlIGNsaW5pY2FsbHkvbWVkaWNhbGx5IG1lYW5pbmdmdWwgcHJlZGljdG9ycy9yZXNwb25zZSB2YXJpYWJsZXMsIGNoZWNrIHRoZW0gZm9yIGFydGlmYWN0cywgY2xlYW4gdXAgdGhvc2UgYXJ0aWZhY3RzIGFzIGFwcHJvcHJpYXRlIGFuZCBwcm9jZWVkIHRvIE1MIG1vZGVsaW5nIHRvIHRlc3Qgb3VyIGh5cG90aGVzZXMuCgpgYGB7ciByZWFkIHRoZSBleHBsYW5hdG9yeSBmaWxlcyB0byBoYW5kbGUgZXZlcnl0aGluZyBpbiBvbmUgcGxhY2UsIGNhY2hlPVRSVUV9CgpwdG08LXByb2MudGltZSgpCmYxX2luZm88LSIvaG9tZS9iaXpvbi9Eb2N1bWVudHMvbXUyYi9udU1vTTJiX0RhdGFzZXRfSW5mb3JtYXRpb24ueGxzeCIKZjJfY29kZTwtIi9ob21lL2Jpem9uL0RvY3VtZW50cy9tdTJiL251TW9NMmJfQ29kZWJvb2tfTklDSERfRGF0YV9DaGFsbGVuZ2UueGxzeCIKbXVfaW5mb19kZiA8LSByZWFkX2V4Y2VsKGYxX2luZm8pCm11X2NvZGVfZGYgPC0gcmVhZF9leGNlbChmMl9jb2RlKQpwcm9jLnRpbWUoKS1wdG0KYGBgCgpOb3cgdGhhdCB3ZSBsb2FkZWQgb3VyIEluZm9ybWF0aW9uIGFuZCBDb2RlIHNwcmVhZHNoZWV0cywgd2UgY2FuIHVzZSBEVCBsaWJyYXJ5IHRvIHZpZXcgYW5kIHNlYXJjaCBpbiB0aGVtIGVhc2lseS4KCipOb3RlOiBpdCBpcyBiZXN0IHRvIG9wZW4gdGhlIG91dHB1dCB0YWJsZXMgaW4gYSBuZXcgd2luZG93LioKCmBgYHtyIHZpZXcgY29kZSBhbmQgaW5mbyB0YWJsZXMsIGNhY2hlPVRSVUV9CmxpYnJhcnkoRFQpCmRhdGF0YWJsZShtdV9jb2RlX2RmLCBmaWx0ZXIgPSAndG9wJywgb3B0aW9ucyA9IGxpc3QocGFnZUxlbmd0aCA9IDEwLCBhdXRvV2lkdGggPSBUUlVFKSkKZGF0YXRhYmxlKG11X2luZm9fZGYsIGZpbHRlciA9ICd0b3AnLCBvcHRpb25zID0gbGlzdChwYWdlTGVuZ3RoID0gMTAsIGF1dG9XaWR0aCA9IFRSVUUpKQpgYGAKCiMgUHJlcHJvY2Vzc2luZwoKW1tJIG5lZWQgdG8gcmVtb3ZlIE5BIHZhbHVlcyBmcm9tICoqQ01BSjAxLioqXXsudWx9XXsuc21hbGxjYXBzfQoKKk5vdGU6IHRoaXMgc3RlcCBjYW4gYmUgcmVwZWF0ZWQgYXMgbmVlZGVkIGluIHRoZSBwcmVwcm9jZXNzaW5nIHBpcGVsaW5lIGZvciB0aGUgZGVzaXJlZCByZXNwb25zZSB2YXJpYWJsZSoKCmBgYHtyIHJlbW92ZSBOQSByb3dzIGZvciBDTUFKMDEsIGNhY2hlPVRSVUV9CiMgc2VsZWN0ICJDTUFKMDEiIHwgaWYgTkEsIHJlbW92ZSB0aGF0IHJvdyAKCiMgcmVtb3ZlIGFsbCByb3dzIHdoZXJlIHRoZSBjb2x1bW4gQ01BSjAxIGhhcyBOQQpjbGVhbl9DTUFKMDFfZGY8LW11X2RhdGFfZGZbIWlzLm5hKG11X2RhdGFfZGYkQ01BSjAxKSwgXSAKCiMgVGhlIGFib3ZlIHdvbid0IHdvcmsgb24gYW4gaDJvIG9iamVjdCwgYnV0IHdvcmtzIG9uIGEgcmVndWxhciBkYXRhZnJhbWUsIHNvIHJlYWRpbmcgYXMgZGYgZmlyc3QgYW5kIHRoZW4sIG9uY2UgcHJlcHJvY2Vzc2luZyBpcyBkb25lLCBtb3Zpbmcgb24gdG8gaDJvCmBgYAoKYGBge3IgcHJlcHJvY2Vzc2luZ19yZXN1bHRzX3ZpZXdfd2l0aF9kcGx5ciwgZWNobz1UUlVFLCBwYWdlZC5wcmludD1UUlVFLCBjYWNoZT1UUlVFfQojVmlldyhjbGVhbl9DTUFKMDFfZGYkQ01BSjAxKSAjc3VwZXIgZmFzdCBFeGNlbC1saWtlIHZpZXcgb2YgdGhlIGRhdGEKCmdsaW1wc2UoY2xlYW5fQ01BSjAxX2RmJENNQUowMSkKY291bnQoY2xlYW5fQ01BSjAxX2RmLCBDTUFKMDEpCm5yb3coY2xlYW5fQ01BSjAxX2RmW211X2RhdGFfZGYkQ01BSjAxXSkKc3VtKGlzLm5hKGNsZWFuX0NNQUowMV9kZiRDTUFKMDEpKQpgYGAKCkNvbmZpcm1pbmcgdGhhdCB3ZSByZW1vdmVkIGFsbCBOQXMgZm9yIENNQUowMToKCiAgICBnbGltcHNlOiBpbnQgWzE6ODc3NF0gMiAyIDIgMiAyIDIgMiAyIDIgMiAuLi4KICAgIGNvdW50OiBbMV0gOTI4OQogICAgc3VtIGlzLm5hOiBbMV0gMAoKKy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKwp8IFJhdyBkYXRhIGNsZWFuZWQgd2l0aCByZWdhcmQgdG8gTkEgdmFsdWVzIGluIENNQUowMSwgbXkgcmVzcG9uc2UgdmFyaWFibGUuIFVzaW5nIHRoaXMgZm9yIG1vZGVsaW5nLiB8CistLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSsKCis9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09KyArLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSsKCistLS0tLS0tLSstLS0tLS0tLSsKfCBDTUFKMDEgfCBuICAgICAgfAp8ICAgICAgICB8ICAgICAgICB8CnwgXDxpbnQ+IHwgXDxpbnQ+IHwKKz09PT09PT06Kz09PT09PT06Kwp8IDEgICAgICB8IDE1NCAgICB8CistLS0tLS0tLSstLS0tLS0tLSsKfCAyICAgICAgfCA4NjIwICAgfAorLS0tLS0tLS0rLS0tLS0tLS0rCgojIExvYWQgaDJvIGZvciBNYWNoaW5lIExlYXJuaW5nCgpgYGB7ciBsb2FkIGgyb30KbGlicmFyeShoMm8pCmgyby5pbml0KCkKYGBgCgpSZWFkaW5nIGluIHRoZSBwcmVwcm9jZXNzZWQgZGF0YXNldC4KCmBgYHtyIHJlYWRfaW50b19oMm8sIGNhY2hlPVRSVUV9CnB0bTwtcHJvYy50aW1lKCkKbXVfZGF0YSA8LSBhcy5oMm8oY2xlYW5fQ01BSjAxX2RmKQpwcm9jLnRpbWUoKS1wdG0KCmBgYAoKIyMgU2V0IHByZWRpY3RvcnMgYW5kIHJlc3BvbnNlOyBzZXQgcmVzcG9uc2UgYXMgYSBmYWN0b3IKCi0gICBtYXRlcm5hbCBvdXRjb21lOiBtb3J0YWxpdHkgKENNQUUxNCk6IGRvbid0IGhhdmUgZW5vdWdoIGRhdGEgdG8gYnVpbGQgYSBiaW5hcnkgb3V0Y29tZSBwcmVkaWN0aW9uIG1vZGVsIFx8IHNraXA7CgotICAgRmlyc3QsIGV4cGxvcmluZyB0aGUgaW1wYWN0IG9mIGRlbW9ncmFwaGljcyBvbiByZWFkbWlzc2lvbiByYXRlIChDTUFKMDEpIGFzIGEgbWVhc3VyZSBvZiBtb3JiaWRpdHk6CgpgYGB7ciBzZXRfcHJlZGljdG9yc19yZXNwb25zZSwgY2FjaGU9VFJVRX0KCm11X2RhdGFbLCJDTUFKMDEiXTwtYXMuZmFjdG9yKG11X2RhdGFbLCJDTUFKMDEiXSkgCgojIHByZWRpY3RvcnMgInBhcnRpY2lwYW50IiwgImRlbW9ncmFwaGljcyIgZGF0YXNldCAoY29sdW1uIEEpIGluY2x1ZGVzIHRoZSByZWxldmFudCBmZWF0dXJlcwojIGNvbmNsdWRlcyB0aGUgdmFyaWFibGVzIGZyb20gImRlbW9ncmFwaGljcyIgZGF0YXNldDsgbW9kZWwgYXMgaXMgYW5kIHRlc3QgbGF0ZXIgYnkgYWRkaW5nIHBhcnRpY2lwYW50IGZlYXR1cmVzIGZyb20gdGhlICJDTUEiIHNldAoKcHJlZGljdG9yczwtYygiQ1JhY2UiLAogICAgICAgICAgICAgICJSYWNlIiwKICAgICAgICAgICAgICAiZVJhY2UiLAogICAgICAgICAgICAgICJlSGlzcGFuaWMiLAogICAgICAgICAgICAgICJCTUkiLAogICAgICAgICAgICAgICJCTUlfQ2F0IiwKICAgICAgICAgICAgICAiRWR1Y2F0aW9uIiwKICAgICAgICAgICAgICAiR3JhdkNhdCIsCiAgICAgICAgICAgICAgIlNtb2tlQ2F0MSIsCiAgICAgICAgICAgICAgIlNtb2tlQ2F0MiIsCiAgICAgICAgICAgICAgIlNtb2tlQ2F0MyIsCiAgICAgICAgICAgICAgIkluc19Hb3Z0IiwKICAgICAgICAgICAgICAiSW5zX01pbCIsCiAgICAgICAgICAgICAgIkluc19Db21tIiwKICAgICAgICAgICAgICAiSW5zX1BlcnMiLAogICAgICAgICAgICAgICJJbnNfT3RociIsCiAgICAgICAgICAgICAgIlBjdEZlZFBvdmVydHkiLAogICAgICAgICAgICAgICJwb3ZlcnR5IiAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICkgI21ha2VzIGEgbGlzdCBvZiBwcmVkaWN0aW5nIHZhcmlhYmxlcwoKcmVzcG9uc2U8LSJDTUFKMDEiCmBgYAoKYGBge3IgdmlldyB0aGUgZGF0YXNldCwgY2FjaGU9VFJVRX0KbXVfZGF0YQpgYGAKCmBgYHtyIG51bWJlciBvZiByb3dzIGZvciBDTUFKMDEsIGNhY2hlPVRSVUV9Cmgyby5ucm93KG11X2RhdGFbIkNNQUowMSJdKQpoMm8uZ3JvdXBfYnkoZGF0YT1tdV9kYXRhLCBieT0iQ01BSjAxIiwgbnJvdygiQ01BSjAxIikpCmBgYAoKSW4gdGhlIHJhdyBkYXRhc2V0LCB3ZSBoYWQgODc4NiBjYXNlcyBvZiAiQ01BRTE0IiBhcmUgY29kZWQgYXMgMiA9IG5vdCBkaWVkLCB0aGVyZSBhcmUgbm8gY2FzZXMgdG8gc3R1ZHkgdGhpcyBvdXRjb21lLgoKSW4gdGhlIHJhdyBkYXRhc2V0LCB3ZSBoYWQgZm9yICJDTUFKMDEiLCB3ZSBoYXZlIGEgc2xpZ2h0bHkgYmV0dGVyIHNpdHVhdGlvbjogMSwgeWVzID0gMTU0IGNhc2VzIG9mIHJlYWRtaXNzaW9uLCAyLCBObywgODYyMCBjYXNlcy4KCk5lZWRlZCB0byBoYW5kbGUgTmFOIGFuZCA0dGggcm93LiBUaGlzIGlzIGJlc3QgZG9uZSBvbiB0aGUgUiBkYXRhIGZyYW1lIHdoaWNoIGlzIHdoYXQgd2UgZGlkIGFib3ZlIGluIFByZXByb2Nlc3Npbmcgc3RlcC4KCmBgYHtyfQpoZWFkKG11X2RhdGEsIGNhY2hlPVRSVUUpCmBgYAoKYGBge3IgaDJvX2FnZ3JlZ2F0b3JfZXN0aW1hdG9yfQojIGFnZyA8LSBoMm8uYWdncmVnYXRvcih0cmFpbmluZ19mcmFtZSA9IG11X2RhdGEsCiMgICAgICAgICAgICAgICAgICAgICAgIHRhcmdldF9udW1fZXhlbXBsYXJzID0gOTAwMCwKIyAgICAgICAgICAgICAgICAgICAgICAgcmVsX3RvbF9udW1fZXhlbXBsYXJzID0gMC41LAojICAgICAgICAgICAgICAgICAgICAgICBjYXRlZ29yaWNhbF9lbmNvZGluZyA9ICJFaWdlbiIpCiMgbmV3X2RmPC1oMm8uYWdncmVnYXRlZF9mcmFtZShhZ2cpCiMgbmV3X2RmCmBgYAoKQWdncmVnYXRvciBkZXN0cm95cyB0aGlzIGRhdGFmcmFtZS4gU2tpcCB0aGlzIHN0ZXAuCgojIFRyYWluIHRoZSBEUkYgbW9kZWwKCihmaXJzdCwgbGV0J3MgZG8gYSAicXVpY2smZGlydHkiIHdheSA9IG5vIGhvbGQtb3V0IGRhdGFzZXQgdG8gZ2V0IHNvbWUgc2Vuc2Ugb2YgdGhlIGRhdGEgZnJvbSBNTCBzdGFuZHBvaW50KQoKYGBge3IgZmlyc3RfZHJmX21vZGVsLCBjYWNoZT1UUlVFfQoKdGVzdF9tb3RoZXJfbXVfZGF0YV9SRm1vZGVsPC1oMm8ucmFuZG9tRm9yZXN0KHg9cHJlZGljdG9ycyx5PXJlc3BvbnNlLHRyYWluaW5nX2ZyYW1lID0gbXVfZGF0YSwgbmZvbGRzPTEwLHNlZWQgPSAxMjM0KQpgYGAKCiMjIFNob3cgdGhlIG1vZGVsJ3MgcGVyZm9ybWFuY2UKCiAgICBBVUNwcjogIDAuOTg1MTc5MSBmb3IgcHJlZGljdGlvbiBvZiBob3NwaXRhbCByZWFkbWlzc2lvbiBmcm9tIGRlbW9ncmFwaGljcwoKYGBge3IgbW9kZWwgcGVyZm9ybWFuY2UsIGVjaG89VFJVRSwgbWVzc2FnZT1UUlVFLCB3YXJuaW5nPUZBTFNFLCBjYWNoZT1UUlVFfQoKdGVzdF9tb3RoZXJfbXVfZGF0YV9SRm1vZGVsCmBgYAoKVGhhdCB3YXMgYSBxdWljayB0ZXN0LgoKTm93IEkgbmVlZCB0byBjcmVhdGUgYSBob2xkLW91dCBkYXRhc2V0IGZpcnN0OyByZXBlYXQgdGhlIGFib3ZlIHRoZW4gYXMgdGVzdDp2YWxpZGF0aW9uLgoKU2VlIFtoZXJlXShodHRwczovL2RvY3MuaDJvLmFpL2gyby9sYXRlc3Qtc3RhYmxlL2gyby1yL2RvY3MvcmVmZXJlbmNlL2luZGV4Lmh0bWwpLCBbaGVyZV0oaHR0cHM6Ly9kb2NzLmgyby5haS9oMm8vbGF0ZXN0LXN0YWJsZS9oMm8tci9kb2NzL3JlZmVyZW5jZS9oMm8uc3BsaXRGcmFtZS5odG1sKSBhbmQgW2hlcmVdKGh0dHA6Ly9oMm8tcmVsZWFzZS5zMy5hbWF6b25hd3MuY29tL2gyby9tYXN0ZXIvMzU1Mi9kb2NzLXdlYnNpdGUvaDJvLWRvY3MvZGF0YW11bmdlL3NwbGl0ZGF0YXNldHMuaHRtbCkKCiMjIFNwbGl0IHRoZSBkYXRhIDAuOCBmb3IgdHJhaW5pbmc6dmFsaWRhdGlvbgoKYGBge3IgZGF0YV9zcGxpdCwgY2FjaGU9VFJVRX0KbW90aGVyX211X2RhdGFfc3BsaXQgPC0gaDJvLnNwbGl0RnJhbWUobXVfZGF0YSwgcmF0aW9zPTAuOCwgc2VlZCA9IDEpCmBgYAoKYGBge3IgYXNzaWduX3RyYWluX3ZhbGlkYXRpb24sIGNhY2hlPVRSVUV9CnRyYWluPW1vdGhlcl9tdV9kYXRhX3NwbGl0W1sxXV0KdmFsaWQ9bW90aGVyX211X2RhdGFfc3BsaXRbWzJdXQpgYGAKCiMjIFRyYWluIHRoZSBEUkYgbW9kZWwgb24gdGhlIHRyYWluaW5nIGRhdGFzZXQgPSAwLjgKCmBgYHtyIHRyYWluX0RSRl9vbl90ZXN0X2RhdGFzZXQsIGNhY2hlPVRSVUV9CnB0bTwtcHJvYy50aW1lKCkKbW90aGVyX211X2RhdGFfUkZtb2RlbDwtaDJvLnJhbmRvbUZvcmVzdCh4PXByZWRpY3RvcnMseT1yZXNwb25zZSx0cmFpbmluZ19mcmFtZSA9IHRyYWluLCBuZm9sZHM9MTAsc2VlZCA9IDEyMzQpCnByb2MudGltZSgpLXB0bQoKYGBgCgojIyBQcmVkaWN0IHVzaW5nIHRoZSBEUkYgbW9kZWwgb24gdGhlIHRlc3RpbmcgZGF0YXNldCA9MC4yCgo+IFlpZWxkcyB2ZXJ5IGdvb2QgQVVDcHI9MC45ODQKCmBgYHtyIHBlcmZvcm1hbmNlIG9mIHRoZSBfcmVhbF8gRFJGIG1vZGVsLCBtZXNzYWdlPVRSVUUsIHBhZ2VkLnByaW50PVRSVUUsIGNhY2hlPVRSVUV9CnB0bTwtcHJvYy50aW1lKCkKCm1vdGhlcl9tdV9kYXRhX3ByZWRpY3Q8LWgyby5wcmVkaWN0KG9iamVjdD1tb3RoZXJfbXVfZGF0YV9SRm1vZGVsLCBuZXdkYXRhPXZhbGlkKQoKbW90aGVyX211X2RhdGFfUkZtb2RlbAoKcHJvYy50aW1lKCktcHRtCmBgYAoKIyMjIEFVUk9DcHIgZm9yIHByZWRpY3RpbmcgcmUtaG9zcGl0YWxpemF0aW9uIGJhc2VkIG9uIHBhdGllbnQgZGVtb2dyYXBoaWNzIGFsb25lCgpgYGB7ciBwbG90IEFVUk9DcHIsIGVjaG89VFJVRSwgY2FjaGU9VFJVRX0KCm1vZD1tb3RoZXJfbXVfZGF0YV9SRm1vZGVsCgpwZXJmIDwtIGgyby5wZXJmb3JtYW5jZShtb2QsdmFsaWQpCgptZXRyaWNzIDwtIGFzLmRhdGEuZnJhbWUoaDJvLm1ldHJpYyhwZXJmKSkKCm1ldHJpY3MgJT4lCiAgZ2dwbG90KGFlcyhyZWNhbGwscHJlY2lzaW9uKSkgKyAKICBnZW9tX2xpbmUoKSArCiAgdGhlbWVfbWluaW1hbCgpCgpgYGAKCiMjIyBFeHBsYWluIHRoZSBtb2RlbAoKYGBge3IgbW9kZWxfcGVyZm9ybWFuY2Vfb25fdGVzdF9kYXRhc2V0LCBjYWNoZT1UUlVFfQoKcHRtPC1wcm9jLnRpbWUoKQoKIyB0b2dnbGUgcHJvZ3Jlc3MgYmFyIGlmIGRlc2lyZWQ6CiMgaDJvLnNob3dfcHJvZ3Jlc3MoKSAKCmV4cCA8LWgyby5leHBsYWluKG9iamVjdD1tb3RoZXJfbXVfZGF0YV9SRm1vZGVsLCBuZXdkYXRhPXZhbGlkKQpwcmludChleHApCgpwcm9jLnRpbWUoKS1wdG0KYGBgCgojIyMgU3RhdGlzdGljcyBzdW1tYXJ5CgpgYGB7ciBmdW5jdGlvbiB0byBzZWUgdGhlIHJlc3VsdHMsIGNhY2hlPVRSVUV9CgpyZXN1bHRzX2RmIDwtIGZ1bmN0aW9uKGgyb19tb2RlbCkgewogIGgyb19tb2RlbEBtb2RlbCRjcm9zc192YWxpZGF0aW9uX21ldHJpY3Nfc3VtbWFyeSAlPiUgCiAgICBhcy5kYXRhLmZyYW1lKCkgJT4lIAogICAgc2VsZWN0KC1tZWFuLCAtc2QpICU+JSAKICAgIHQoKSAlPiUgCiAgICBhcy5kYXRhLmZyYW1lKCkgJT4lIAogICAgbXV0YXRlX2FsbChhcy5jaGFyYWN0ZXIpICU+JSAKICAgIG11dGF0ZV9hbGwoYXMubnVtZXJpYykgLT4gawogIAogIGsgJT4lIAogICAgc2VsZWN0KEFjY3VyYWN5ID0gYWNjdXJhY3ksCiAgICAgICAgICAgcHJBVUMgPSBwcl9hdWMsCiAgICAgICAgICAgUHJlY2lzaW9uID0gcHJlY2lzaW9uLAogICAgICAgICAgIFNwZWNpZmljaXR5ID0gc3BlY2lmaWNpdHksCiAgICAgICAgICAgUmVjYWxsID0gcmVjYWxsLAogICAgICAgICAgIExvZ2xvc3MgPSBsb2dsb3NzKSAlPiUgCiAgcmV0dXJuKCkKfQpgYGAKCmBgYHtyIHByb2R1Y2luZyB0aGUgc3RhdGlzdGljcyBzdW1tYXJ5LCBjYWNoZT1UUlVFfQoKIyBVc2luZyBmdW5jdGlvbgpyZXN1bHRzX2RmKG1vZCkgLT4gb3V0Y29tZQoKIyBPdXRjb21lIApvdXRjb21lICU+JSAKICBnYXRoZXIoTWV0cmljcywgVmFsdWVzKSAlPiUgCiAgZ2dwbG90KGFlcyhNZXRyaWNzLCBWYWx1ZXMsIGZpbGwgPSBNZXRyaWNzLCBjb2xvciA9IE1ldHJpY3MpKSArCiAgZ2VvbV9ib3hwbG90KGFscGhhID0gMC4zLCBzaG93LmxlZ2VuZCA9IEZBTFNFKSArIAogIGZhY2V0X3dyYXAofiBNZXRyaWNzLCBzY2FsZXMgPSAiZnJlZSIpICsgCiAgbGFicyh0aXRsZSA9ICJQZXJmb3JtYW5jZSBvZiBvdXIgTUwgbW9kZWwgdXNpbmcgSDJvIHBhY2thZ2UgIiwKICAgICAgIGNhcHRpb24gPSAiRGF0YSBTb3VyY2U6IE5JQ0hEIERlY29kaW5nIE1hdGVybmFsIE1vcmJpZGl0eSBEYXRhIENoYWxsZW5nZVxuQ3JlYXRlZCBieSBNYXJ0aW4gRnJhc2NoIChmdXJ0aGVyIGNyZWRpdCB0byBodHRwczovL2JpdC5seS8zQnBQcWNiKSIpICsKICB0aGVtZV9taW5pbWFsKCkKYGBgCgpgYGB7ciBUYWJ1bGFyIHN0YXRpc3RpY3Mgc3VtbWFyeSwgY2FjaGU9VFJVRX0KCiMgU3RhdGlzdGljcyBzdW1tYXJ5Cm91dGNvbWUgJT4lIAogIGdhdGhlcihNZXRyaWNzLCBWYWx1ZXMpICU+JSAKICBncm91cF9ieShNZXRyaWNzKSAlPiUgCiAgc3VtbWFyaXNlX2VhY2goZnVucyhtZWFuLCBtZWRpYW4sIG1pbiwgbWF4LCBzZCwgbigpKSkgJT4lIAogIG11dGF0ZV9pZihpcy5udW1lcmljLCBmdW5jdGlvbih4KSB7cm91bmQoMTAwKngsIDIpfSkgJT4lCiAga25pdHI6OmthYmxlKGNvbC5uYW1lcyA9IGMoIkNyaXRlcmlvbiIsICJNZWFuIiwgIk1lZGlhbiIsICJNaW4iLCAiTWF4IiwgIlNEIiwgIk4iKSkKCmBgYAoKIyMgQnVpbGRpbmcgYW4gaW50ZXJwcmV0YWJsZSBkZWNpc2lvbiB0cmVlIG1vZGVsCgo+IFVzaW5nIHRoZSBgYmFsYW5jZV9jbGFzc2VzYCB0ZXJtIGF2YWlsYWJsZSBpbiB0aGlzIG1vZGVsIGFsbG93cyB0byBiZXR0ZXIgY29udHJvbCBmb3IgdGhlIHVuYmFsYW5jZWQgZGF0YSB3ZSBhcmUgZGVhbGluZyB3aXRoIGhlcmUsIGJlY2F1c2UgdGhlIHJlLWhvc3BpdGFsaXphdGlvbiByYXRlIGlzIFx+MiUKCltgU291cmNlYF0oaHR0cDovL3JzdHVkaW8tcHVicy1zdGF0aWMuczMuYW1hem9uYXdzLmNvbS80NjM2NTNfYjUwNTc5ZjA1YWUyNDZhOWJmYTQyNTFlZjlhYWUyNmIuaHRtbCNwbG90dGluZy1kZWNpc2lvbi10cmVlLXdpdGgtZGF0YS50YWJsZSkKCmBgYHtyIGRlY2lzaW9uX3RyZWVfbW9kZWwsIGNhY2hlPVRSVUV9CgptYXRlcm5hbF9kdF9tb2RlbDwtaDJvLmdibSh4PXByZWRpY3RvcnMseT1yZXNwb25zZSx0cmFpbmluZ19mcmFtZSA9IHRyYWluLCB2YWxpZGF0aW9uX2ZyYW1lID0gdmFsaWQsIGJhbGFuY2VfY2xhc3NlcyA9IFRSVUUsIHNlZWQgPSAxMjM0LCBuZm9sZHM9MTApCgojIEdCTSBoeXBlcnBhcmFtdGVycwpnYm1fcGFyYW1zID0gbGlzdChtYXhfZGVwdGggPSBzZXEoMiwgMTApKQoKIyBUcmFpbiBhbmQgdmFsaWRhdGUgYSBjYXJ0ZXNpYW4gZ3JpZCBvZiBHQk1zCmdibV9ncmlkID0gaDJvLmdyaWQoImdibSIsIHggPSBwcmVkaWN0b3JzLCB5ID0gcmVzcG9uc2UsCiAgICAgICAgICAgICAgICAgICAgZ3JpZF9pZCA9ICJnYm1fZ3JpZF8xdHJlZTgiLAogICAgICAgICAgICAgICAgICAgIHRyYWluaW5nX2ZyYW1lID0gdHJhaW4sCiAgICAgICAgICAgICAgICAgICAgdmFsaWRhdGlvbl9mcmFtZSA9IHZhbGlkLAogICAgICAgICAgICAgICAgICAgIGJhbGFuY2VfY2xhc3NlcyA9IFRSVUUsCiAgICAgICAgICAgICAgICAgICAgbnRyZWVzID0gMSwgbWluX3Jvd3MgPSAxLCBzYW1wbGVfcmF0ZSA9IDEsIGNvbF9zYW1wbGVfcmF0ZSA9IDEsCiAgICAgICAgICAgICAgICAgICAgbGVhcm5fcmF0ZSA9IC4wMSwgc2VlZCA9IDEyMzQsIAogICAgICAgICAgICAgICAgICAgIGh5cGVyX3BhcmFtcyA9IGdibV9wYXJhbXMpCgpnYm1fZ3JpZHBlcmYgPSBoMm8uZ2V0R3JpZChncmlkX2lkID0gImdibV9ncmlkXzF0cmVlOCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHNvcnRfYnkgPSAiYXVjIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgZGVjcmVhc2luZyA9IFRSVUUpCgpgYGAKCmBgYHtyIEdCTV9wZXJmb3JtYW5jZSwgY2FjaGU9VFJVRX0KCiMgd2hhdCBpcyB0aGUgcGVyZm9ybWFuY2Ugb2YgdGhpcyBHQk0/Cm1hdGVybmFsX2R0X21vZGVsCmBgYAoKV2Ugb2J0YWluIHByQVVDPTAuOTgKCmBgYHtyIEdCTV9ncmlkX3BlcmZvcm1hbmNlLCBjYWNoZT1UUlVFfQpnYm1fZ3JpZHBlcmYKYGBgCgpJbmZsZWN0aW9uIHBvaW50IGlzIGF0IG1heF9kZXB0aD04CgpgYGB7ciB0cmFpbiBHQk0sIGNhY2hlPVRSVUV9CgptYXRlcm5hbF8xX3RyZWUgPSBoMm8uZ2JtKHggPSBwcmVkaWN0b3JzLCB5ID0gcmVzcG9uc2UsIAogICAgICAgICAgICAgICAgICAgICAgICB0cmFpbmluZ19mcmFtZSA9IHRyYWluLCBiYWxhbmNlX2NsYXNzZXMgPSBUUlVFLAogICAgICAgICAgICAgICAgICAgICAgICBudHJlZXMgPSAxLCBtaW5fcm93cyA9IDEsIHNhbXBsZV9yYXRlID0gMSwgY29sX3NhbXBsZV9yYXRlID0gMSwKICAgICAgICAgICAgICAgICAgICAgICAgbWF4X2RlcHRoID0gOCwKICAgICAgICAgICAgICAgICAgIyB1c2UgZWFybHkgc3RvcHBpbmcgb25jZSB0aGUgdmFsaWRhdGlvbiBBVUMgZG9lc24ndCBpbXByb3ZlIGJ5IGF0IGxlYXN0IDAuMDElCiAgICAgICAgICAgICAgICAgICMgZm9yIDUgY29uc2VjdXRpdmUgc2NvcmluZyBldmVudHMKICAgICAgICAgICAgICAgICAgICAgICAgc3RvcHBpbmdfcm91bmRzID0gMywgc3RvcHBpbmdfdG9sZXJhbmNlID0gMC4wMSwgCiAgICAgICAgICAgICAgICAgICAgICAgIHN0b3BwaW5nX21ldHJpYyA9ICJBVUMiLCAKICAgICAgICAgICAgICAgICAgICAgICAgc2VlZCA9IDEpCm1hdGVybmFsXzFfdHJlZQoKCmBgYAoKICAgIEFVQ1BSOiAgMC44ODI3NjIKCmBgYHtyIG1hdGVybmFsX3RyZWUsIGNhY2hlPVRSVUV9Cm1hdGVybmFsX1RyZWUgPSBoMm8uZ2V0TW9kZWxUcmVlKG1vZGVsID0gbWF0ZXJuYWxfMV90cmVlLCB0cmVlX251bWJlciA9IDEpCmBgYAoKYGBge3IgZnVuY3Rpb25fdml6X3RyZWUsIGNhY2hlPVRSVUV9CgojIFZpc3VhbGl6aW5nIEgyTyBUcmVlcwoKbGlicmFyeShkYXRhLnRyZWUpCgpjcmVhdGVEYXRhVHJlZSA8LSBmdW5jdGlvbihoMm9UcmVlKSB7CiAgCiAgaDJvVHJlZVJvb3QgPSBoMm9UcmVlQHJvb3Rfbm9kZQogIAogIGRhdGFUcmVlID0gTm9kZSRuZXcoaDJvVHJlZVJvb3RAc3BsaXRfZmVhdHVyZSkKICBkYXRhVHJlZSR0eXBlID0gJ3NwbGl0JwogIAogIGFkZENoaWxkcmVuKGRhdGFUcmVlLCBoMm9UcmVlUm9vdCkKICAKICByZXR1cm4oZGF0YVRyZWUpCn0KCmFkZENoaWxkcmVuIDwtIGZ1bmN0aW9uKGR0cmVlLCBub2RlKSB7CiAgCiAgaWYoY2xhc3Mobm9kZSlbMV0gIT0gJ0gyT1NwbGl0Tm9kZScpIHJldHVybihUUlVFKQogIAogIGZlYXR1cmUgPSBub2RlQHNwbGl0X2ZlYXR1cmUKICBpZCA9IG5vZGVAaWQKICBuYV9kaXJlY3Rpb24gPSBub2RlQG5hX2RpcmVjdGlvbgogIAogIGlmKGlzLm5hKG5vZGVAdGhyZXNob2xkKSkgewogICAgbGVmdEVkZ2VMYWJlbCA9IHByaW50VmFsdWVzKG5vZGVAbGVmdF9sZXZlbHMsIG5hX2RpcmVjdGlvbj09J0xFRlQnLCA0KQogICAgcmlnaHRFZGdlTGFiZWwgPSBwcmludFZhbHVlcyhub2RlQHJpZ2h0X2xldmVscywgbmFfZGlyZWN0aW9uPT0nUklHSFQnLCA0KQogIH1lbHNlIHsKICAgIGxlZnRFZGdlTGFiZWwgPSBwYXN0ZSgiPCIsIG5vZGVAdGhyZXNob2xkLCBpZmVsc2UobmFfZGlyZWN0aW9uPT0nTEVGVCcsJyxOQScsJycpKQogICAgcmlnaHRFZGdlTGFiZWwgPSBwYXN0ZSgiPj0iLCBub2RlQHRocmVzaG9sZCwgaWZlbHNlKG5hX2RpcmVjdGlvbj09J1JJR0hUJywnLE5BJywnJykpCiAgfQogIAogIGxlZnRfbm9kZSA9IG5vZGVAbGVmdF9jaGlsZAogIHJpZ2h0X25vZGUgPSBub2RlQHJpZ2h0X2NoaWxkCiAgCiAgaWYoY2xhc3MobGVmdF9ub2RlKVtbMV1dID09ICdIMk9MZWFmTm9kZScpCiAgICBsZWZ0TGFiZWwgPSBwYXN0ZSgicHJlZGljdGlvbjoiLCBsZWZ0X25vZGVAcHJlZGljdGlvbikKICBlbHNlCiAgICBsZWZ0TGFiZWwgPSBsZWZ0X25vZGVAc3BsaXRfZmVhdHVyZQogIAogIGlmKGNsYXNzKHJpZ2h0X25vZGUpW1sxXV0gPT0gJ0gyT0xlYWZOb2RlJykKICAgIHJpZ2h0TGFiZWwgPSBwYXN0ZSgicHJlZGljdGlvbjoiLCByaWdodF9ub2RlQHByZWRpY3Rpb24pCiAgZWxzZQogICAgcmlnaHRMYWJlbCA9IHJpZ2h0X25vZGVAc3BsaXRfZmVhdHVyZQogIAogIGlmKGxlZnRMYWJlbCA9PSByaWdodExhYmVsKSB7CiAgICBsZWZ0TGFiZWwgPSBwYXN0ZShsZWZ0TGFiZWwsICIoTCkiKQogICAgcmlnaHRMYWJlbCA9IHBhc3RlKHJpZ2h0TGFiZWwsICIoUikiKQogIH0KICAKICBkdHJlZUxlZnQgPSBkdHJlZSRBZGRDaGlsZChsZWZ0TGFiZWwpCiAgZHRyZWVMZWZ0JGVkZ2VMYWJlbCA9IGxlZnRFZGdlTGFiZWwKICBkdHJlZUxlZnQkdHlwZSA9IGlmZWxzZShjbGFzcyhsZWZ0X25vZGUpWzFdID09ICdIMk9TcGxpdE5vZGUnLCAnc3BsaXQnLCAnbGVhZicpCiAgCiAgZHRyZWVSaWdodCA9IGR0cmVlJEFkZENoaWxkKHJpZ2h0TGFiZWwpCiAgZHRyZWVSaWdodCRlZGdlTGFiZWwgPSByaWdodEVkZ2VMYWJlbAogIGR0cmVlUmlnaHQkdHlwZSA9IGlmZWxzZShjbGFzcyhyaWdodF9ub2RlKVsxXSA9PSAnSDJPU3BsaXROb2RlJywgJ3NwbGl0JywgJ2xlYWYnKQogIAogIGFkZENoaWxkcmVuKGR0cmVlTGVmdCwgbGVmdF9ub2RlKQogIGFkZENoaWxkcmVuKGR0cmVlUmlnaHQsIHJpZ2h0X25vZGUpCiAgCiAgcmV0dXJuKEZBTFNFKQp9CgpwcmludFZhbHVlcyA8LSBmdW5jdGlvbih2YWx1ZXMsIGlzX25hX2RpcmVjdGlvbiwgbj00KSB7CiAgbCA9IGxlbmd0aCh2YWx1ZXMpCiAgCiAgaWYobCA9PSAwKQogICAgdmFsdWVfc3RyaW5nID0gaWZlbHNlKGlzX25hX2RpcmVjdGlvbiwgIk5BIiwgIiIpCiAgZWxzZQogICAgdmFsdWVfc3RyaW5nID0gcGFzdGUwKHBhc3RlMCh2YWx1ZXNbMTptaW4obixsKV0sIGNvbGxhcHNlID0gJywgJyksCiAgICAgICAgICAgICAgICAgICAgICAgICAgaWZlbHNlKGwgPiBuLCAiLC4uLiIsICIiKSwKICAgICAgICAgICAgICAgICAgICAgICAgICBpZmVsc2UoaXNfbmFfZGlyZWN0aW9uLCAiLCBOQSIsICIiKSkKICAKICByZXR1cm4odmFsdWVfc3RyaW5nKQp9CgoKYGBgCgpUaGlzIGRlY2lzaW9uIHRyZWUsIGFsc28gc3VwcGxpZWQgYXMgUERGLCBpcyBtZWFudCB0byBoZWxwIGJ1aWxkIGludHVpdGlvbiBhYm91dCBob3cgdGhlIG1vZGVsLgoKYGBge3IgZGVjaXNpb25fdHJlZV9oMm8sIGNhY2hlPVRSVUV9CgpsaWJyYXJ5KERpYWdyYW1tZVIpCgojIGN1c3RvbWl6ZWQgRFQgZm9yIG91ciBIMk8gbW9kZWwKCm1hdGVybmFsX211MkRhdGFUcmVlID0gY3JlYXRlRGF0YVRyZWUobWF0ZXJuYWxfVHJlZSkKCkdldEVkZ2VMYWJlbCA8LSBmdW5jdGlvbihub2RlKSB7cmV0dXJuIChub2RlJGVkZ2VMYWJlbCl9CkdldE5vZGVTaGFwZSA8LSBmdW5jdGlvbihub2RlKSB7c3dpdGNoKG5vZGUkdHlwZSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNwbGl0ID0gImRpYW1vbmQiLCBsZWFmID0gIm92YWwiKX0KR2V0Rm9udE5hbWUgPC0gZnVuY3Rpb24obm9kZSkge3N3aXRjaChub2RlJHR5cGUsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNwbGl0ID0gJ1BhbGF0aW5vLWJvbGQnLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsZWFmID0gJ1BhbGF0aW5vJyl9ClNldEVkZ2VTdHlsZShtYXRlcm5hbF9tdTJEYXRhVHJlZSwgZm9udG5hbWUgPSAnUGFsYXRpbm8taXRhbGljJywgCiAgICAgICAgICAgICBsYWJlbCA9IEdldEVkZ2VMYWJlbCwgbGFiZWxmbG9hdCA9IFRSVUUsCiAgICAgICAgICAgICBmb250c2l6ZSA9ICIyNiIsIGZvbnRjb2xvcj0ncm95YWxibHVlNCcpClNldE5vZGVTdHlsZShtYXRlcm5hbF9tdTJEYXRhVHJlZSwgZm9udG5hbWUgPSBHZXRGb250TmFtZSwgc2hhcGUgPSBHZXROb2RlU2hhcGUsIAogICAgICAgICAgICAgZm9udHNpemUgPSAiMjYiLCBmb250Y29sb3I9J3JveWFsYmx1ZTQnLAogICAgICAgICAgICAgaGVpZ2h0PSIwLjc1Iiwgd2lkdGg9IjEiKQoKU2V0R3JhcGhTdHlsZShtYXRlcm5hbF9tdTJEYXRhVHJlZSwgcmFua2RpciA9ICJMUiIsIGRwaT03MC4pCgpwbG90KG1hdGVybmFsX211MkRhdGFUcmVlLCBvdXRwdXQgPSAiZ3JhcGgiKQoKYGBgCgpgYGB7ciBnZW5lcmF0ZSBoMm8gZXhwbGFuYXRpb24gb2YgdGhlIERUIG1vZGVsLCBjYWNoZT1UUlVFfQpwdG08LXByb2MudGltZSgpCgpleHBfZHQ8LWgyby5leHBsYWluKG1hdGVybmFsX2R0X21vZGVsLHZhbGlkKQoKcHJvYy50aW1lKCktcHRtCmBgYAoKYGBge3Igc2hvdyB0aGUgZXhwbGFuYXRpb24sIGNhY2hlPVRSVUV9CgpleHBfZHQKYGBgCgojIyBVc2luZyAqKk5hw692ZSBCYXllcyBDbGFzc2lmaWVyKioKCmBgYHtyIE5hw692ZSBCYXllcyBDbGFzc2lmaWVyLCBjYWNoZT1UUlVFfQoKIyBCdWlsZCBhbmQgdHJhaW4gdGhlIG1vZGVsOgptbzJiX25iIDwtIGgyby5uYWl2ZUJheWVzKHggPSBwcmVkaWN0b3JzLAogICAgICAgICAgICAgICAgICAgICAgICAgIHkgPSByZXNwb25zZSwKICAgICAgICAgICAgICAgICAgICAgICAgICB0cmFpbmluZ19mcmFtZSA9IHRyYWluLAogICAgICAgICAgICAgICAgICAgICAgICAgIGxhcGxhY2UgPSAwLAogICAgICAgICAgICAgICAgICAgICAgICAgIG5mb2xkcyA9IDEwLAogICAgICAgICAgICAgICAgICAgICAgICAgIHNlZWQgPSAxMjM0KQoKIyBFdmFsIHBlcmZvcm1hbmNlOgpwZXJmIDwtIGgyby5wZXJmb3JtYW5jZShtbzJiX25iKQoKIyBHZW5lcmF0ZSB0aGUgcHJlZGljdGlvbnMgb24gYSB0ZXN0IHNldCAoaWYgbmVjZXNzYXJ5KToKcHJlZCA8LSBoMm8ucHJlZGljdChtbzJiX25iLCBuZXdkYXRhID0gdmFsaWQpCmBgYAoKYGBge3IgYmF5ZXNfcGVyZm9ybWFuY2UsIGNhY2hlPVRSVUV9CnBlcmYKYGBgCgogICAgQVVDOiAgMC42MTc4MjQ1CiAgICBBVUNQUjogIDAuOTg3Nzc0OAoKYGBge3IgZXhwbGFpbiBuYWl2ZSBiYXllcyBtb2RlbCwgY2FjaGU9VFJVRX0KCiMgYmVzdCB2aWV3ZWQgaW4gYSBuZXcgd2luZG93IG9yIHNlZSwgcGxlYXNlLCB0aGUgUERGIGluY2x1ZGVkIHdpdGggdGhlIHN1Ym1pc3Npb24KZXhwX25iIDwtIGgyby5leHBsYWluKG1vMmJfbmIsdmFsaWQpCmBgYAoKTm90ZSB0aGUgaGlnaGx5IHZhcmlhYmxlIHBhcnRpYWwgaW1wb3J0YW5jZSBvZiB0aGUgZGlmZmVyZW50IHNvY2lvLWRlbW9ncmFwaGljIGNoYXJhY3RlcmlzdGljcwoKYGBge3Igdml6X25iX3BlcmZvcm1hbmNlLCBjYWNoZT1UUlVFfQoKZXhwX25iCmBgYAoKIyMgRXh0ZW5kaW5nIHRoZSBNTCB0b29sYm94OiBVc2luZyBoMm8gQXV0b01MIG1vZGUgdG8gZmluZCBhbiBvYmplY3RpdmVseSBiZXN0IHBlcmZvcm1pbmcgbW9kZWwKCkNvbXBhcmUgW2hlcmVdKGh0dHA6Ly9oMm8tcmVsZWFzZS5zMy5hbWF6b25hd3MuY29tL2gyby9yZWwteWF0ZXMvMS9kb2NzLXdlYnNpdGUvaDJvLWRvY3MvYXV0b21sLmh0bWwjY29kZS1leGFtcGxlcykuIFRoZSBmaW5kaW5ncyBhcmUgdG8gYmUgaW50ZXJwcmV0ZWQgd2l0aCBjYXV0aW9uIGF0IHRoaXMgc3RhZ2UuIE9uY2Ugd2Ugb2J0YWluIGEgbGFyZ2VyIGV4dGVybmFsIGRhdGFzZXQgZm9yIHZhbGlkYXRpb24sIHdpdGggYSBtb3JlIGJhbGFuY2VkIGNhc2UgZGlzdHJpYnV0aW9uLCB0aGlzIHdpbGwgYmVjb21lIG1vcmUgdXNlZnVsIGFuZCBhbGxvdyBidWlsZGluZyBhbiBpbmZlcmVuY2UgZW5naW5lIHRoYXQgY291bGQgYmUgZGVwbG95ZWQgZm9yIHVzZS4gSSBhbSBwcmVzZW50aW5nIHRoaXMgY29kZSB0aGVyZWZvcmUgYXMgYSByZWZlcmVuY2UgZm9yIGZ1dHVyZSB3b3JrLgoKTmV2ZXJ0aGVsZXNzLCBpdCBpcyBldmlkZW50IHRoYXQgYW4gb3B0aW1pemF0aW9uIGV2ZW4gYXQgdGhpcyBzdGFnZSByZXN1bHRzIGluIGEgbWFya2VkIChcfjYwJSkgaW1wcm92ZW1lbnQgb2YgY2xhc3NpZmljYXRpb24gcHJlZGljdGlvbiBwZXJmb3JtYW5jZSwgdXAgdG8gQVVDIDc2JS4gVGhpcyByZXN1bHQgY2FuIHZhcnkgZGVwZW5kaW5nIG9uIHRoZSBydW4uCgpOb3RlIHBsZWFzZSwgdGhpcyBjb2RlIHJ1bnMgZm9yIFx+MiBob3VycyBvbiBhIHdlbGwtZXF1aXBwZWQgZGVlcCBsZWFybmluZyB3b3Jrc3RhdGlvbi4KCiMjIyBUcmFpbiBpbiBBdXRvTUwgbW9kZQoKYGBge3IgYXV0b19tbCwgY2FjaGU9VFJVRX0KCnB0bTwtcHJvYy50aW1lKCkKCm1hdGVybmFsX2FtbCA8LSBoMm8uYXV0b21sKHg9cHJlZGljdG9ycyx5PXJlc3BvbnNlLHRyYWluaW5nX2ZyYW1lID0gdHJhaW4sIG1heF9tb2RlbHMgPSAyMCwgc2VlZCA9IDEpCgptYXRlcm5hbF9sYiA8LSBtYXRlcm5hbF9hbWxAbGVhZGVyYm9hcmQKCiNwcmludChtYXRlcm5hbF9sYiwgbiA9IG5yb3cobWF0ZXJuYWxfbGIpKSAjUHJpbnQgYWxsIHJvd3MgaW5zdGVhZCBvZiBkZWZhdWx0IDYgcm93cwoKcHJvYy50aW1lKCktcHRtCmBgYAoKIyMjIFZhbGlkYXRlIHRoZSBjb21iaW5lZCBsZWFkZXIgYW1sIG1vZGVsCgpgYGB7ciB2YWxpZGF0ZV9hbWwsIGNhY2hlPVRSVUV9CnB0bTwtcHJvYy50aW1lKCkKCm1hdGVybmFsX3BlcmZfdmFsaWQgPC0gaDJvLnBlcmZvcm1hbmNlKG1hdGVybmFsX2FtbEBsZWFkZXIsbmV3ZGF0YT12YWxpZCx4dmFsPUZBTFNFLHZhbGlkPVRSVUUpCgpwcmVkIDwtIGgyby5wcmVkaWN0KG1hdGVybmFsX2FtbEBsZWFkZXIsIHZhbGlkKQpgYGAKCmBgYHtyIGFtbF9hdWMsIGNhY2hlPVRSVUV9Cmgyby5hdWMobWF0ZXJuYWxfYW1sQGxlYWRlcikKYGBgCgpgYGB7ciBhbWxfbWV0cmljcywgY2FjaGU9VFJVRX0KIyBVc2luZyBmdW5jdGlvbgpyZXN1bHRzX2RmKG1hdGVybmFsX2FtbEBsZWFkZXIpIC0+IG91dGNvbWUKCiMgT3V0Y29tZSAKb3V0Y29tZSAlPiUgCiAgZ2F0aGVyKE1ldHJpY3MsIFZhbHVlcykgJT4lIAogIGdncGxvdChhZXMoTWV0cmljcywgVmFsdWVzLCBmaWxsID0gTWV0cmljcywgY29sb3IgPSBNZXRyaWNzKSkgKwogIGdlb21fYm94cGxvdChhbHBoYSA9IDAuMywgc2hvdy5sZWdlbmQgPSBGQUxTRSkgKyAKICBmYWNldF93cmFwKH4gTWV0cmljcywgc2NhbGVzID0gImZyZWUiKSArIAogIGxhYnModGl0bGUgPSAiUGVyZm9ybWFuY2Ugb2YgdGhlIGJlc3QgQXV0b01MIG1vZGVsIHVzaW5nIEgybyBwYWNrYWdlICIsCiAgICAgICBjYXB0aW9uID0gIkRhdGEgU291cmNlOiBOSUNIRCBEZWNvZGluZyBNYXRlcm5hbCBNb3JiaWRpdHkgRGF0YSBDaGFsbGVuZ2VcbkNyZWF0ZWQgYnkgTWFydGluIEZyYXNjaCAoZnVydGhlciBjcmVkaXQgdG8gaHR0cHM6Ly9iaXQubHkvM0JwUHFjYikiKSArCiAgdGhlbWVfbWluaW1hbCgpCmBgYAoKW2BFeHBsYWluIHRoZSBtb2RlbGBdKGh0dHBzOi8vZG9jcy5oMm8uYWkvaDJvL2xhdGVzdC1zdGFibGUvaDJvLWRvY3MvZXhwbGFpbi5odG1sKQoKV2Ugb2JzZXJ2ZSBubyBzcGVjaWZpY2l0eSBiZWNhdXNlIHRoZSBkYXRhc2V0IGlzIHVuYmFsYW5jZWQgc3VjaCB0aGF0IGJ5IGx1Y2sgb2YgZHJhdyAod2hlbiB0aGUgZGF0YXNldCBpcyBzcGxpdCA4MDoyMCkgd2UgZ2V0IG5vIHRydWUgcG9zaXRpdmVzLgoKYGBge3IgZXhwbGFpbl9iZXN0X21vZGVsLCBjYWNoZT1UUlVFfQpwdG08LXByb2MudGltZSgpCmV4cCA8LWgyby5leHBsYWluKG1hdGVybmFsX2FtbEBsZWFkZXIsIHZhbGlkKQpwcm9jLnRpbWUoKS1wdG0KYGBgCgpgYGB7ciBleHBsYWluLCBjYWNoZT1UUlVFfQpwcmludChleHApCmBgYAoKIyBHUFUgYWNjZWxlcmF0ZWQgTUwgc29sdXRpb25zCgpUZXN0IHJ1biBmcm9tIHRoZSBbZGVtb10oaHR0cHM6Ly9kb2NzLmgyby5haS9oMm8vbGF0ZXN0LXN0YWJsZS9oMm8tci9kb2NzL3JlZmVyZW5jZS9oMm8ucmFuZG9tRm9yZXN0Lmh0bWwpIHNpdGUuIERldGFpbHMgb24gc2V0dXAgW2hlcmVdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9oMm80Z3B1L3ZpZ25ldHRlcy9nZXR0aW5nX3N0YXJ0ZWQuaHRtbCkuCgojIyMgVGVzdGluZyBoMm8gR1BVIGluc3RhbGxhdGlvbgoKUmVxdWlyZXMgUHl0aG9uIGgybzRncHUgbW9kdWxlLiBEZXRhaWxzIG9mIGluc3RhbGxhdGlvbiBbaGVyZV0oaHR0cHM6Ly9naXRodWIuY29tL2gyb2FpL2gybzRncHUpLgoKLSAgIFRoZSBSIGxpYnJhcnkgdXNlcyByZXRpY3VsYXRlIHRvIHBvaW50IHRvIHRoZSBjb3JyZWN0IFB5dGhvbiB2ZXJzaW9uICgzLjYpCgotICAgW1ZpZ25ldHRlc10oaHR0cHM6Ly9naXRodWIuY29tL2gyb2FpL2gybzRncHUvYmxvYi9tYXN0ZXIvc3JjL2ludGVyZmFjZV9yL3ZpZ25ldHRlcy9nZXR0aW5nX3N0YXJ0ZWQuUm1kKSBcfCBQeXRob24gW2RlbW9zXShodHRwczovL2dpdGh1Yi5jb20vaDJvYWkvaDJvNGdwdS90cmVlL21hc3Rlci9leGFtcGxlcy9weS9kZW1vcykKCmBgYHtyIGEgcmVsZXZhbnQgZXhhbXBsZSBvZiBHUFUtYWNjZWxlcmF0ZWQgTUwsIGNhY2hlPVRSVUV9CmxpYnJhcnkoaDJvNGdwdSkKbGlicmFyeShyZXRpY3VsYXRlKSAgIyBvbmx5IG5lZWRlZCBpZiB1c2luZyBhIHZpcnR1YWwgUHl0aG9uIGVudmlyb25tZW50IG9yIG11bHRpcGxlIFB5dGhvbiB2ZXJzaW9ucwp1c2VfcHl0aG9uKCIvaG9tZS9iaXpvbi9hbmFjb25kYTMvYmluL3B5dGhvbjMuNiIsIHJlcXVpcmVkID0gVFJVRSkKI3VzZV92aXJ0dWFsZW52KCIvaG9tZS9iaXpvbi9oMm9fbWwiKSAgIyBzZXQgdGhpcyB0byB0aGUgcGF0aCBvZiB5b3VyIHZlbnYgaWYgYW55IGlzIHVzZWQKCiMgU2V0dXAgZGF0YXNldAp4IDwtIGlyaXNbMTo0XQp5IDwtIGFzLmludGVnZXIoaXJpcyRTcGVjaWVzKSAtIDEKCiMgSW5pdGlhbGl6ZSBhbmQgdHJhaW4gdGhlIGNsYXNzaWZpZXIKbW9kZWwgPC0gaDJvNGdwdS5yYW5kb21fZm9yZXN0X2NsYXNzaWZpZXIoKSAlPiUgZml0KHgsIHkpCgojICBPdGhlciBNTCBhcHByb2FjaGVzIGFyZSBhbHNvIGF2YWlsYWJsZSBzdWNoIGFzIEdCTSwgcmVncmVzc2lvbiBtb2RlbHMsIHVuc3VwZXJ2aXNlZCBsZWFybmluZwoKIyBNYWtlIHByZWRpY3Rpb25zCnByZWRpY3Rpb25zIDwtIG1vZGVsICU+JSBwcmVkaWN0KHgpCmBgYAoKIyMjIERlcGxveWluZyBHUFUtYWNjZWxlcmF0ZWQgTUwgb24gb3VyIGRhdGFzZXQKCkkgYW0gbGVhdmluZyB0aGlzIGZvciBmdXR1cmUgaW1wbGVtZW50YXRpb25zLiBUaGlzIGlzIHNpbXBseSB0byBwb2ludCBvdXQgdGhlIHBvc3NpYmlsaXR5IHRvIGxldmVyYWdlIEdQVS1lcXVpcHBlZCBkZWVwIGxlYXJuaW5nIHdvcmtzdGF0aW9ucyB0byBzcGVlZCB1cCB0aGUgbW9kZWwgYnVpbGRpbmcgdGltZSBvbiBhIGRhdGFzZXQgb2YgdGhpcyBzaXplLgoKPT4gU2VlICJwb3N0cGFydHVtIGRlcHJlc3Npb24iIG5vdGVib29rIGZvciBhZGRpdGlvbmFsIG1vZGVscy4KCmBgYHtyLCBjYWNoZT1UUlVFfQpzZXNzaW9uSW5mbygpCmBgYAo=